/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "VertexBuffer.h"
#include "Hardware.h"
#include <logs/Log.h>
#include <Gluck.h>
#include <math/utl.h>

//#define GL_GLEXT_PROTOTYPES
#include <glext.h>

static PFNGLBINDBUFFERARBPROC glBindBufferARB = 0;
static PFNGLGENBUFFERSARBPROC glGenBuffersARB = 0;
static PFNGLBUFFERDATAARBPROC glBufferDataARB = 0;
static PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = 0;
static PFNGLMAPBUFFERARBPROC glMapBufferARB = 0;
static PFNGLUNMAPBUFFERARBPROC glUnmapBufferARB = 0;

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace macx
{

VertexBuffer::VertexBuffer(Hardware * hardware, bool dynamic )
:	valid_flag( false ),
	vertex_buffer( 0 ),
	vertices_count( 0 ),
	stride( 0 ),
	dynamic( dynamic ),
	normals_stride( 0 ),
	colors_stride( 0 ),
	uvs_stride( 0 )
{
	static int first_time = 0;
	if( !first_time )
	{
		glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
		glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
		glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
		glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
		glMapBufferARB = (PFNGLMAPBUFFERARBPROC) wglGetProcAddress("glMapBufferARB");
		glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC) wglGetProcAddress("glUnmapBufferARB");
		first_time = 1;
	}
	glGenBuffersARB( 1, &vertex_buffer );
	if ( !vertex_buffer )
		throw Gluck(__FUNCTION__, "create_internal_vertex_buffer() failed");
}

VertexBuffer::~VertexBuffer()
{
	valid_flag = false;

	if( vertex_buffer )
		glDeleteBuffersARB( 1, &vertex_buffer );
	vertex_buffer = 0;
}

bool VertexBuffer::is_valid()const
{
	return valid_flag;
}

bool VertexBuffer::update(
		const SimpleArray<float> & positions,
		const SimpleArray<float> & normals,
		const SimpleArray<float> & u_tangents,
		const SimpleArray<float> & v_tangents,
		const SimpleArray<float> & uv0,
		const SimpleArray<float> & uv1,
		const SimpleArray<float> & weights0,
		const SimpleArray<float> & weights1,
		const SimpleArray<float> & weights2,
		const SimpleArray<int> & indices0,
		const SimpleArray<int> & indices1,
		const SimpleArray<int> & indices2,
		const SimpleArray<int> & indices3
		)
{
	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);
	stride += 3 * sizeof(float);
	stride += 2 * sizeof(float);

	normals_stride = 3*sizeof(GLfloat);
	uvs_stride = 6*sizeof(GLfloat);
	colors_stride = 0;

	if( vertices_count == 0 )
	{
		this->vertices_count = vertices_count;
		valid_flag = true;
		return true;
	}

	glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertex_buffer );
	if( !valid_flag || this->vertices_count != vertices_count )
	{
		this->vertices_count = vertices_count;

		glBufferDataARB( GL_ARRAY_BUFFER_ARB, stride * vertices_count, 0, dynamic? GL_STREAM_DRAW_ARB : GL_STATIC_DRAW_ARB ); 
	}

	float * data = (float *)glMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY );
	if( !data )
		throw Gluck(__FUNCTION__, "vertex_buffer->Lock() failed");

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
			*data++ = 0;
			*data++ = 0;
			*data++ = 1;
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}

	}
	if( !glUnmapBufferARB( GL_ARRAY_BUFFER_ARB ) )
	{
		valid_flag = false;
		return false;
	}

	valid_flag = true;
	return true;
}

bool VertexBuffer::update(
					const SimpleArray<float> & positions,
					const SimpleArray<float> & normals,
					const SimpleArray<float> & colors,
					const SimpleArray<float> & uv0,
					const SimpleArray<float> & uv1
					)
{
	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);

	normals_stride = 0;
	uvs_stride = 0;
	colors_stride = 0;

	if( normals.size() )
	{
		normals_stride = stride;
		stride += 3 * sizeof(float);
	}
	if( colors.size() )
	{
		colors_stride = stride;
		stride += 4 * sizeof(unsigned char);
	}
	uvs_stride = stride;
	stride += 2 * sizeof(float);

	if( vertices_count == 0 )
	{
		this->vertices_count = vertices_count;
		valid_flag = true;
		return true;
	}

	glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertex_buffer );
	if( !valid_flag || this->vertices_count != vertices_count )
	{
		this->vertices_count = vertices_count;

		glBufferDataARB( GL_ARRAY_BUFFER_ARB, stride * vertices_count, 0, dynamic? GL_STREAM_DRAW_ARB : GL_STATIC_DRAW_ARB ); 
	}

	float * data = (float *)glMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY );
	if( !data )
		throw Gluck(__FUNCTION__, "vertex_buffer->Lock() failed");

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator colit = colors.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( colors.size() != vertices_count*4 )
		{
		}
		else
		{
			unsigned char * char_data = (unsigned char *)data;
			*char_data++ = math::clamp<float>( (*(colit+2))*255, 0, 255 );
			*char_data++ = math::clamp<float>( (*(colit+1))*255, 0, 255 );
			*char_data++ = math::clamp<float>( (*colit)*255, 0, 255 );
			colit += 3;
			*char_data++ = math::clamp<float>( (*colit++)*255, 0, 255 );

			*data++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}

	}
	if( !glUnmapBufferARB( GL_ARRAY_BUFFER_ARB ) )
	{
		valid_flag = false;
		return false;
	}

	valid_flag = true;
	return true;
}

bool VertexBuffer::render( int vertex_pos, int primitive_count )
{
	if( vertices_count == 0 /*|| !hardware->is_operational()*/ )
		return true;

	if( vertex_pos + 3 * primitive_count > vertices_count )
	{
		logs::log() << logs::error << "Vertex buffer overflow - " << primitive_count << " primitives at position " << vertex_pos << " while the total number of vertices is " << vertices_count << "\n";
		return false;
	}

	glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertex_buffer );
	glVertexPointer( 3, GL_FLOAT, stride, BUFFER_OFFSET(0) );
	if( normals_stride )
	{
		glNormalPointer( GL_FLOAT, stride, BUFFER_OFFSET( normals_stride ) );
		glEnableClientState( GL_NORMAL_ARRAY );
		glEnable( GL_LIGHTING );
	}
	else
	{
		glDisableClientState( GL_NORMAL_ARRAY );
		glDisable( GL_LIGHTING );
	}

	if( colors_stride )
	{
		glColorPointer( 4, GL_UNSIGNED_BYTE, stride, BUFFER_OFFSET( colors_stride ) );
		glEnableClientState( GL_COLOR_ARRAY );
	}
	else
		glDisableClientState( GL_COLOR_ARRAY );

	glTexCoordPointer( 2, GL_FLOAT, stride, BUFFER_OFFSET( uvs_stride ) );

	glDrawArrays( GL_TRIANGLES, vertex_pos, primitive_count * 3 );
	return true;
}

bool VertexBuffer::render( hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
	return true;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, int vertex_pos, int primitive_count )
{
	return true;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
	return true;
}

} // namespace macx
} // namespace hard
} // namespace lib3d
} // namespace liba

